﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using ChiWalker.Utilities;
using Styx;
using Styx.TreeSharp;

namespace ChiWalker.Dynamics
{
    //Full credit to who ever wrote the Belphegor CompositeBuilder on which this is based
    internal class SpecBuilder
    {
        private static readonly List<MethodInfo> Methods = new List<MethodInfo>();

        public static Composite GetSpecRoutine(WoWClass cClass, WoWSpec spec, BehaviorType behavior,
            out int behaviourCount)
        {
            behaviourCount = 0;
            if (Methods.Count <= 0)
            {
                Logger.WriteVerbose("Building Method List");
                foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
                {
                    Methods.AddRange(type.GetMethods(BindingFlags.Static | BindingFlags.Public));
                }
            }
            Logger.WriteVerbose("Added {0} methods", Methods.Count);

            var matchedMethods = new Dictionary<int, PrioritySelector>();

            foreach (MethodInfo mInfo in Methods.Where(
                mInfo => !mInfo.IsGenericMethod &&
                         mInfo.GetParameters().Length == 0).Where(mInfo =>
                             mInfo.ReturnType == typeof (Composite) ||
                             mInfo.ReturnType == typeof (Composite)))
            {
                bool classMatchs = false;
                bool specMathces = false;
                bool behaviorMatches = false;
                int thePriority = 0;
                bool hasIgnore = false;
                var theBehaviourType = BehaviorType.All;
                var theIgnoreType = BehaviorType.All;
                try
                {
                    foreach (object ca in mInfo.GetCustomAttributes(false))
                    {
                        if (ca is ClassAttribute)
                        {
                            var attrib = ca as ClassAttribute;

                            if (attrib.CharClass != cClass)
                                continue;

                            classMatchs = true;
                        }
                        else if (ca is SpecAttribute)
                        {
                            var attrib = ca as SpecAttribute;

                            if (attrib.CharacterSpec != spec)
                                continue;

                            specMathces = true;
                        }
                        else if (ca is BehaviorAttribute)
                        {
                            var attrib = ca as BehaviorAttribute;
                            if ((attrib.Type & behavior) == 0)
                            {
                                continue;
                            }
                            theBehaviourType = attrib.Type;
                            behaviourCount++;
                            behaviorMatches = true;
                        }
                        else if (ca is PriorityAttribute)
                        {
                            var attrib = ca as PriorityAttribute;
                            thePriority = attrib.PriorityLevel;
                        }
                        else if (ca is IgnoreBehaviorCountAttribute)
                        {
                            var attrib = ca as IgnoreBehaviorCountAttribute;
                            hasIgnore = true;
                            theIgnoreType = attrib.Type;
                        }
                    }
                }
                catch (Exception)
                {
                    Logger.Write("Error getting custom attributes for " + mInfo.Name);
                    continue;
                }
                if (behaviorMatches && hasIgnore && theBehaviourType == theIgnoreType)
                {
                    behaviourCount--;
                }

                // If all our attributes match, then mark it as wanted!
                if (classMatchs && specMathces && behaviorMatches)
                {
                    //Logger.Write("Using {0} for {1}", mInfo.Name, behavior);
                    Composite matched;
                    try
                    {
                        matched = (Composite) mInfo.Invoke(null, null);
                    }
                    catch (Exception e)
                    {
                        Logger.Write("ERROR Creating composite: {0}\n{1}", mInfo.Name, e.StackTrace);
                        continue;
                    }
                    if (!matchedMethods.ContainsKey(thePriority))
                    {
                        matchedMethods.Add(thePriority, new PrioritySelector(matched));
                    }
                    else
                    {
                        matchedMethods[thePriority].AddChild(matched);
                    }
                }
            }
            // If we found no methods, rofls!
            if (matchedMethods.Count <= 0)
            {
                return null;
            }

            // Return the composite match we found. (Note: ANY composite return is fine)
            return matchedMethods.OrderByDescending(mm => mm.Key).First().Value;
        }
    }
}